@file:OptIn(ExperimentalFoundationApi::class, ExperimentalFoundationApi::class)

package com.example.result.books

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.Settings
import android.webkit.ConsoleMessage
import android.webkit.WebChromeClient
import android.webkit.WebView
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.text.isDigitsOnly
import coil.decode.SvgDecoder
import coil.request.ImageRequest
import com.example.result.R
import com.example.result.player.PlayerScreen
import com.example.result.ui.theme.LocalMessage
import com.example.result.view.FullScreenImage
import com.example.result.view.FullScreenSvgImage
import com.lly.markdown.Error
import com.lly.markdown.ErrorData
import com.lly.markdown.Heading
import com.lly.markdown.HeadingData
import com.lly.markdown.MarkdownData
import com.lly.markdown.MarkdownDataContainer
import com.lly.markdown.MarkdownItem
import com.lly.markdown.MarkdownListener
import com.lly.markdown.MarkdownScreen
import com.lly.markdown.MarkdownServer
import com.lly.markdown.OtherData
import com.lly.tboc.zhouyi.Screen
import com.lly.tboc.zhouyi.Screen.GuaEdit.isAssets
import com.lly.tboc.zhouyi.Screen.WebView.suffix
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.nio.charset.Charset

@Composable
fun WebViewScreen(
    modifier: Modifier = Modifier,
    fileAddress: Screen.WebView.FileAddress,
    fileOperation: FileOperation
) {

    val path = fileAddress.path
    val current = LocalContext.current
    val fileTypeMapDataStore = current.fileTypeMapDataStore
    val suffix = path.suffix()
    val type by
    fileTypeMapDataStore.data.map {
        it.map[suffix]?.let { Screen.FileType.valueOf(it) } ?: Screen.FileType.UnKnow
    }
        .collectAsState(initial = Screen.FileType.UnKnow)
    val needShowSelectDialog = remember {
        mutableStateOf(false)
    }

    if (needShowSelectDialog.value) {
        SelectFileTypeDialog(suffix, fileTypeMapDataStore, needShowSelectDialog)
    }
//    Log.e("WebViewScreen", "suffix:$suffix type:$type")
    when (type) {
        Screen.FileType.Bitmap -> {
            FullScreenImage(modifier, url = path)
        }

        Screen.FileType.Svg -> {
            SvgShowAndEdit(fileAddress, fileOperation, modifier)
        }

        Screen.FileType.Markdown -> {
            MarkdownShowAndEdit(fileAddress, fileOperation, modifier)
        }

        Screen.FileType.Dot -> {
            DotShowAndEdit(fileAddress, fileOperation, modifier)
        }

        Screen.FileType.Txt -> {
            TextShowAndEdit(fileAddress, fileOperation, modifier)
        }

        Screen.FileType.UnKnow -> {
            Column(
                modifier = modifier
                    .fillMaxHeight()
                    .fillMaxWidth(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Button(onClick = {
                    needShowSelectDialog.value = true
                }) {
                    Text(text = "选择文件类型")
                    Text(text = suffix)
                }
            }
        }

        Screen.FileType.Audio,
        Screen.FileType.Video -> PlayerScreen(
            uri = when (fileAddress) {
                is Screen.WebView.FileAddress.Path -> Uri.parse(fileAddress.path)
                is Screen.WebView.FileAddress.Uri -> fileAddress.uri
            }
        )
    }
}


val encodes = arrayOf(
    Charsets.UTF_8,
    Charsets.ISO_8859_1,
    Charset.forName("GB2312"),
    Charsets.UTF_16,
    Charsets.UTF_16BE,
    Charsets.UTF_16LE,
    Charsets.US_ASCII,
    Charsets.UTF_32
).map { it.name() }

abstract class FileOperationAdapter {

    abstract fun saveToFile()
}

@Composable
fun ShowOrEdit(
    modifier: Modifier,
    currentEncode: String,
    whenEncodeSelected: (String) -> Unit,
    fileOperationAdapter: FileOperationAdapter,
    startOptions: @Composable RowScope.() -> Unit = {},
    endOptions: @Composable RowScope.() -> Unit = {},
    sourceView: @Composable ColumnScope.() -> Unit,
    renderView: @Composable ColumnScope.() -> Unit,
) {
    val context = LocalContext.current
    var isShowSource by remember {
        mutableStateOf(false)
    }
    Column(
        modifier = modifier.fillMaxHeight()
    ) {
        var selectEncode by remember {
            mutableStateOf(false)
        }
        DropdownMenu(expanded = selectEncode, onDismissRequest = { selectEncode = false }) {
            encodes.forEach {
                DropdownMenuItem(
                    onClick = {
                        selectEncode = false
                        whenEncodeSelected(it)
                    },
                    text = { Text(text = it) }
                )
            }
        }

        Row(
            modifier = Modifier
                .fillMaxWidth()
                .horizontalScroll(rememberScrollState()),
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            startOptions()
            Button(onClick = { isShowSource = !isShowSource }) {
                Text(text = if (!isShowSource) "源码" else "渲染")
            }
            Button(onClick = { selectEncode = true }) {
                Text(text = currentEncode)
            }
            val hasStorageManagerPermission =
                Build.VERSION.SDK_INT < Build.VERSION_CODES.R || Environment.isExternalStorageManager()
            Button(onClick = {
                if (hasStorageManagerPermission) {
                    fileOperationAdapter.saveToFile()
                } else {
                    kotlin.runCatching {
                        context.startActivity(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION))
                    }
                }
            }, enabled = true) {
                Text(text = "保存${hasStorageManagerPermission}")
            }
            endOptions()
        }
        HorizontalDivider(modifier = Modifier.padding(8.dp))
        if (isShowSource) {
            sourceView()
        } else {
            renderView()
        }
    }
}

@Composable

fun DefaultSourceEdit(
    source: State<String>,
    needSyncSourceChanged: Boolean,
    outScope: CoroutineScope = rememberCoroutineScope(),
    onValueChange: (String) -> Unit
) {
    var texts by remember {
        mutableStateOf(emptyList<List<MutableState<String>>>())
    }
    var showLineNumber by remember {
        mutableStateOf(false)
    }
    var showLineNumberView by remember {
        mutableStateOf(false)
    }

    LaunchedEffect(source.value, showLineNumber) {
        withContext(Dispatchers.IO) {
            texts = if (showLineNumber) {
                source.value.split('\n')
            } else {
                listOf(source.value)
            }.map {
                it.chunked(CHUNCK).map { mutableStateOf(it) }
            }
            showLineNumberView = showLineNumber
        }
    }
    var hasChanged by remember {
        mutableStateOf(false)
    }
    val scope = rememberCoroutineScope()
    val message = LocalMessage.current
    DisposableEffect(key1 = source, needSyncSourceChanged) {
        onDispose {
            if (hasChanged) {
                message.msg.value = "修改开始同步，清等待"
                outScope.launch(Dispatchers.IO) {
                    val content =
                        texts.fold(StringBuilder()) { acc, strings ->
                            if (acc.isNotEmpty()) {
                                acc.append("\n")
                            }
                            strings.forEach {
                                acc.append(it.value)
                            }
                            acc
                        }.toString()
                    onValueChange(content)
                    message.msg.value = "修改同步完成"
                }

            }
        }
    }
    val listState = rememberLazyListState()
    var jumpLineNo by remember {
        mutableStateOf(0)
    }
    LazyRow {
        stickyHeader {
            TextField(
                modifier = Modifier.width(200.dp),
                value = "$jumpLineNo",
                prefix = { Text(text = "第") },
                suffix = { Text(text = if (showLineNumberView) "行" else "块") },
                label = {
                    Text(
                        text = "共" + if (showLineNumberView) "${texts.size}" else
                            texts.getOrNull(0)?.size.toString()

                    )
                },
                trailingIcon = {
                    Button(onClick = { scope.launch { listState.scrollToItem(jumpLineNo) } }) {
                        Text(text = "跳转")
                    }
                },
                keyboardOptions = KeyboardOptions.Default.copy(
                    keyboardType = KeyboardType.Number,
                    imeAction = ImeAction.Go
                ),
                keyboardActions = KeyboardActions {
                    scope.launch { listState.scrollToItem(jumpLineNo) }
                },
                onValueChange = {
                    if (it.isNotEmpty() && it.isDigitsOnly()) {
                        jumpLineNo = it.toInt()
                    }
                })

        }
        item {
            TextButton(onClick = { showLineNumber = !showLineNumber }) {
                Switch(
                    checked = showLineNumber,
                    onCheckedChange = {
                        showLineNumber = it
                    },
                    thumbContent = if (showLineNumber) {
                        {
                            Icon(
                                Icons.Filled.Check, contentDescription = null,
                                modifier = Modifier.size(SwitchDefaults.IconSize),
                            )
                        }
                    } else {
                        null
                    }
                )
                Text(text = "显示行号")
            }

        }
    }
    val actionOnValueChanged = { s: String, oId: Int, iId: Int ->
        texts[oId][iId].value = s
        hasChanged = true
    }
    if (showLineNumberView) {
        LineTextEditView(listState, texts, actionOnValueChanged)
    } else {
        if (texts.isNotEmpty()) {
            ChunkTextEditView(listState, texts, actionOnValueChanged)
        }
    }
}

@Composable
private fun LineTextEditView(
    listState: LazyListState,
    texts: List<List<MutableState<String>>>,
    onValueChange: (String, Int, Int) -> Unit
) {
    LazyColumn(modifier = Modifier.fillMaxWidth(), state = listState) {
        itemsIndexed(texts) { oId, line ->
            Row {
                Text(text = "${oId + 1}")
                Column {
                    line.forEachIndexed { iId, l ->
                        TextField(
                            modifier = Modifier
                                .fillMaxWidth(),
                            value = l.value, onValueChange = {
                                onValueChange(it, oId, iId)
                            }, textStyle = MaterialTheme.typography.bodyLarge
                        )
                    }
                }
            }
        }
    }
}

@Composable
private fun ChunkTextEditView(
    listState: LazyListState,
    texts: List<List<MutableState<String>>>,
    onValueChange: (String, Int, Int) -> Unit
) {
    LazyColumn(modifier = Modifier.fillMaxWidth(), state = listState) {
        itemsIndexed(texts[0]) { iId, l ->
            TextField(
                modifier = Modifier
                    .fillMaxWidth(),
                value = l.value, onValueChange = {
                    onValueChange(it, 0, iId)
                }, textStyle = MaterialTheme.typography.bodyLarge
            )
        }
    }
}


@Composable
fun SvgShowAndEdit(
    fileAddress: Screen.WebView.FileAddress,
    fileOperation: FileOperation,
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    val source = remember(fileAddress) {
        mutableStateOf("")
    }
    val currentEncode by getEncode(context = context, fileAddress = fileAddress)
    extractedSource(fileAddress, source, context, Charset.forName(currentEncode))
    val renameDialogState = mutableStateOf(false)
    RenameDialog(renameDialogState, fileAddress, fileOperation, source)
    var needSyncSourceChanged by remember {
        mutableStateOf(false)
    }
    val fileOperationAdapter = object : FileOperationAdapter() {
        override fun saveToFile() {
            needSyncSourceChanged = !needSyncSourceChanged
            renameDialogState.value = true
        }
    }
    val scope = rememberCoroutineScope()
    ShowOrEdit(modifier, currentEncode, updateEncode(scope, context, fileAddress),
        fileOperationAdapter,
        sourceView = {
            DefaultSourceEdit(
                source = source,
                needSyncSourceChanged,
                outScope = scope,
                onValueChange = { source.value = it })
        }) {
        FullScreenSvgImage(
            model = ImageRequest.Builder(context)
                .data(source.value.toByteArray())
                .decoderFactory(SvgDecoder.Factory())
                .crossfade(true)
                .build(),
            placeholder = painterResource(R.drawable.taiji),
            contentDescription = "svg",
        )
    }
}

@Composable
internal fun RenameDialog(
    renameDialogState: MutableState<Boolean>,
    fileAddress: Screen.WebView.FileAddress,
    fileOperation: FileOperation,
    source: MutableState<String>
) {
    if (renameDialogState.value) {

        val filename = fileAddress.path.let {
            it.substring(
                maxOf(it.indexOfLast { '/' == it } + 1, 0)
            )
        }
        RenameDialog(
            showState = renameDialogState,
            defaultName = filename
        ) { f ->
            fileOperation.saveToFile(source.value, f)
        }
    }
}

@Composable
fun extractedSource(
    fileAddress: Screen.WebView.FileAddress,
    source: MutableState<String>,
    context: Context,
    encode: Charset
) {
    LaunchedEffect(fileAddress, encode) {
        source.value = withContext(Dispatchers.IO) {
            when (fileAddress) {
                is Screen.WebView.FileAddress.Path -> fileAddress.path.let { path ->
                    kotlin.runCatching {
                        if (path.isAssets()) {
                            context.resources.assets.open(path.replace(ASSET_PREFIX, ""))
                                .use { it.reader(encode).readText() }
                        } else {
                            File(path).readText(encode)
                        }
                    }.getOrElse {
                        it.printStackTrace()
                        ""
                    }
                }

                is Screen.WebView.FileAddress.Uri -> {
                    context.contentResolver.openInputStream(fileAddress.uri)
                        ?.use { it.bufferedReader(encode).readText() } ?: ""
                }

            }
        }
    }
}


@Composable
fun MarkdownShowAndEdit(
    fileAddress: Screen.WebView.FileAddress,
    fileOperation: FileOperation,
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    val source = remember(fileAddress) {
        mutableStateOf("")
    }

    val currentEncode by getEncode(context = context, fileAddress = fileAddress)


    extractedSource(
        fileAddress = fileAddress,
        source = source,
        context = context,
        encode = Charset.forName(currentEncode)
    )
    val renameDialogState = mutableStateOf(false)
    RenameDialog(renameDialogState, fileAddress, fileOperation, source)
    var needSyncSourceChanged by remember {
        mutableStateOf(false)
    }

    val fileOperationAdapter = object : FileOperationAdapter() {
        override fun saveToFile() {
            needSyncSourceChanged = !needSyncSourceChanged

            renameDialogState.value = true
        }

    }

    ShowOrEdit(modifier, currentEncode, updateEncode(scope, context, fileAddress),
        fileOperationAdapter,
        sourceView = {
            DefaultSourceEdit(
                source = source,
                needSyncSourceChanged,
                outScope = scope,
                onValueChange = { source.value = it })
        }) {
        MarkdownShow(fileAddress, source, context)
    }

}

@Composable
internal fun MarkdownShow(
    fileAddress: Screen.WebView.FileAddress,
    source: MutableState<String>,
    context: Context
) {
    var content by remember(fileAddress) {
        mutableStateOf(MarkdownDataContainer())
    }
    val scope = rememberCoroutineScope()
    LaunchedEffect(source.value) {
        withContext(Dispatchers.IO) {
            val listener = object : MarkdownListener {
                val markdown = mutableListOf<MarkdownData>()

                var count = 0
                override fun whenParseItem(item: MarkdownItem) {
                    scope.launch {
                        reduceMarkdown(it = item, acc = markdown)
                        count++
                        if (count % 20 == 0) {
                            content = MarkdownDataContainer(markdown)
                        }
                    }
                }

            }
            content =
                MarkdownServer.parse(source.value + "\n", listener)
                    .fold(mutableListOf<MarkdownData>()) { acc, it ->
                        reduceMarkdown(it, acc)
                        acc
                    }.let { MarkdownDataContainer(it) }
        }
    }
    val msg = LocalMessage.current.msg
    MarkdownScreen(
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colorScheme.primaryContainer)
            .padding(8.dp),
        markdownData = content.list,
        fileAddress.path.let { it.substring(0, it.lastIndexOf('/') + 1) }
    ) {
        runCatching {
            val uri = Uri.parse(it)
            context.startActivity(Intent(Intent.ACTION_VIEW, uri))
        }.onFailure {
            scope.launch {
                it.message?.let {
                    msg.emit(it)
                }
            }
        }
    }
}

private fun reduceMarkdown(
    it: MarkdownItem,
    acc: MutableList<MarkdownData>
) {
    when (it) {
        is Heading -> {
            acc.add(HeadingData(it, acc.size))
        }

        is Error -> {
            acc.add(ErrorData(it, acc.size))
        }

        else -> {
            val list: OtherData =
                if (acc.isNotEmpty() && acc.last() is OtherData) {
                    acc.last() as OtherData
                } else {
                    val l = if (acc.isNotEmpty()) {
                        acc.last().headingLevels + 1
                    } else {
                        1
                    }
                    OtherData(mutableListOf(), l, acc.size).apply {
                        acc.add(this)
                    }
                }
            list.items.add(it)
        }
    }
}

const val CHUNCK = 1000 * 3


fun String.subStringByLineBreakAndLengthMostAt(n: Int) = trimStart().take(n).let {
    val s = it
    val i = s.indexOf("\n")
    if (i > 0) {
        s.substring(0, i)
    } else {
        s
    }
}

@Composable
fun DotShowAndEdit(
    fileAddress: Screen.WebView.FileAddress,
    fileOperation: FileOperation,
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    val source = remember(fileAddress) {
        mutableStateOf("")
    }
    val currentEncode by getEncode(context = context, fileAddress = fileAddress)
    val scope = rememberCoroutineScope()
    extractedSource(
        fileAddress = fileAddress,
        source = source,
        context = context,
        encode = Charset.forName(currentEncode)
    )

    val renameDialogState = mutableStateOf(false)
    RenameDialog(renameDialogState, fileAddress, fileOperation, source)
    var needSyncSourceChanged by remember {
        mutableStateOf(false)
    }

    val fileOperationAdapter = object : FileOperationAdapter() {
        override fun saveToFile() {
            needSyncSourceChanged = !needSyncSourceChanged

            renameDialogState.value = true
        }

    }
    ShowOrEdit(modifier, currentEncode, updateEncode(scope, context, fileAddress),
        fileOperationAdapter,
        sourceView = {
            DefaultSourceEdit(
                source = source,
                needSyncSourceChanged,
                outScope = scope,
                onValueChange = { source.value = it })
        }) {

        DotShowView(context, source.value)
    }

}

@Composable
fun DotShowView(
    context: Context,
    source: String
) {
    var consoleMessage by remember {
        mutableStateOf("")
    }
    val webView = getWebView(context) {
        consoleMessage = it
    }
    if (consoleMessage.isNotEmpty()) {
        var showMessage by remember {
            mutableStateOf(false)
        }
        SuggestionChip(onClick = { showMessage = !showMessage }, label = {
            Text(text = if (!showMessage) "显示日志" else "隐藏日志")
        })
        if (showMessage) {
            Column(
                modifier = Modifier
                    .heightIn(max = 200.dp)
                    .verticalScroll(rememberScrollState())
            ) {
                Text(text = consoleMessage)
            }
        }
    }
    AndroidView(factory = {
        webView
    }, update = {
        webView.loadUrl("file:///android_asset/graphviz.html")
    })
    LaunchedEffect(key1 = source) {
        withContext(Dispatchers.IO) {
            delay(1000)
            val content =
                source.replace("""//.*?\n""".toRegex(), "").replace("""/\*.*?\*/""".toRegex(), "")
                    .replace("\n", "\\n")
            withContext(Dispatchers.Main) {
                webView.evaluateJavascript("renderDotCode('${content}');", null)
            }
        }
    }
}

@SuppressLint("SetJavaScriptEnabled")
@Composable
private fun getWebView(context: Context, onConsoleMessage: (String) -> Unit = {}): WebView {
    val webView = remember {
        WebView(context).apply {
            setBackgroundColor(Color.TRANSPARENT)
            settings.setSupportZoom(true)
            settings.javaScriptEnabled = true
            settings.useWideViewPort = true//设定支持 viewport
            settings.loadWithOverviewMode = true   //自适应屏幕
            settings.builtInZoomControls = true
            settings.displayZoomControls = false

            webChromeClient = object : WebChromeClient() {
                override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
                    onConsoleMessage(consoleMessage?.message() ?: "")
//                    Log.d("WebView Console", consoleMessage?.message() ?: "")
                    return true
                }
            }
            /*
                        webViewClient = object : WebViewClient() {

                        }
            */
        }
    }
    return webView
}

